[Android]SMS認証のユーザー負担を減らすSMS User Consent APIを使ってみた
アプリがSMSから認証コードを取得して自動入力するSMS User Consent APIを使ってみた。
はじめに
サービスのセキュリティを高める方法としてSMSを利用した本人認証があります。SMS認証はセキュリティが向上する反面、電話番号と認証コードの入力が必要なためユーザーにとっては煩わしいです。当記事では、ユーザーの負担を減らすための手段としてSMS User Consent APIを紹介します。このAPIを使うことでパーミッションの追加なしでSMS受信時にユーザーがワンタップするだけでアプリはSMSから認証コードを取得することができます。(サーバーサイドとSMS Retriever APIを組み合わせることで完全に自動で認証コードを取得することが出来ますが、今回は扱いません)
今回実装したソースコードはこちら↓
- (GitHub)sms-user-consent-api-sample
開発環境
- OS: macOS Catalina
- Android Studio: 4.1.2
- Language: Kotlin 1.4.21
制限事項
SMS User Consent APIには、いくつかの制限があります。以下のケースではSMSを受信してもユーザーに許可を求めるダイアログは表示されません。
- SMSに数字を1つ以上含んでいる4~10桁の英数字が存在しない
- ユーザーの連絡先に登録されている差出人からのメッセージ
実装手順
- ライブラリを追加する
- レイアウトファイルの編集
- MainActivityの実装
アプリレベルのbuild.gradleにライブラリを追加
アプリレベルのbuild.gradleに以下に示した2つのライブラリを追加します。
app/build.gradle
dependencies { // 省略 implementation "com.google.android.gms:play-services-auth:19.0.0" implementation "com.google.android.gms:play-services-auth-api-phone:17.5.0" }
レイアウトの編集
activity_main.xmlに認証コードを入力するEditTextを配置します。
src/main/res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <EditText android:id="@+id/editTextTextPersonName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:hint="Enter verification code" android:inputType="text" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
MainActivityの編集
SMS受信時のおおまかな流れを説明します。端末がSMSを受信するとシステムがBroadcastReceiverのonReceiveメソッドを呼び出します。startActivityForResultメソッドを使ってアプリ上に確認ダイアログを表示します。ユーザーが確認ダイアログのAllowボタンをタップするとonActivityResultメソッドが呼び出されます。ここでSMSメッセージを解析して認証コードを取得します。今回のサンプルアプリではEditTextに取得した認証コードを表示しましたが、代わりにサーバーサイドへ認証コードを送信することでさらにユーザー操作を減らすことができます。
src/main/java/com/mos1210/android/example/smsuserconsent/MainActivity.kt
import android.app.Activity import android.content.* import android.os.Bundle import android.util.Log import android.widget.EditText import androidx.appcompat.app.AppCompatActivity import com.google.android.gms.auth.api.phone.SmsRetriever import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.common.api.Status class MainActivity : AppCompatActivity() { companion object { val TAG: String = MainActivity::class.java.simpleName private const val REQUEST_CODE = 1 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } override fun onResume() { super.onResume() val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION) registerReceiver(smsVerificationReceiver, intentFilter) // SMSメッセージの待ち受け開始 SmsRetriever.getClient(this).startSmsUserConsent(null) } override fun onPause() { super.onPause() // SMSメッセージの待ち受け解除 unregisterReceiver(smsVerificationReceiver) } private val smsVerificationReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (SmsRetriever.SMS_RETRIEVED_ACTION == intent?.action && intent.extras != null ) { val status = intent.extras?.get(SmsRetriever.EXTRA_STATUS) as Status when (status.statusCode) { CommonStatusCodes.SUCCESS -> { val consentIntent = intent.extras?.getParcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT) try { startActivityForResult(consentIntent, REQUEST_CODE) } catch (e: ActivityNotFoundException) { e.printStackTrace() } } CommonStatusCodes.TIMEOUT -> { Log.d(TAG, "timeout") // 5分でタイムアウト } } } } } public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { REQUEST_CODE -> if (resultCode == Activity.RESULT_OK && data != null) { val message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE) ?: return val oneTimeCode = parseOneTimeCode(message) findViewById<EditText>(R.id.editTextVerificationCode).setText(oneTimeCode) } } } private fun parseOneTimeCode(message: String): String { // SMSメッセージに合わせて文字列を処理する return message.split("\n")[0].split(":")[1] } }
検証方法
エミューレーターに対してadbコマンドでSMSを送信します。最初にエミュレータを起動してからTerminalでadb devices
コマンドを入力しテスト対象となるデバイスを調べます。
$ adb devices List of devices attached emulator-5554 device ←対象のエミュレーター
次に、以下のadbコマンドで対象のEmulatorにSMSを送信します。この例では電話番号が「09012345678」、SMSの内容が「verification code:123456」です。
$ adb -s emulator-5554 emu sms send 09012345678 verification code:123456 OK
実行結果
SMS受信時に確認ダイアログが表示され、ユーザーがAllowボタンをタップすると認証コードが自動入力されました。
まとめ
SMS User Consent APIを使ってSMSから認証コードを取得して自動入力することができました。もっと別な方法があるよ!などあればTwitterやコメントで教えていただければ嬉しいです。